home *** CD-ROM | disk | FTP | other *** search
/ Aminet 52 / Aminet 52 (2002)(GTI - Schatztruhe)[!][Dec 2002].iso / Aminet / docs / mags / saku15.lha / Teksti / C-kurssi.txt < prev    next >
Text File  |  1995-11-05  |  36KB  |  1,000 lines

  1. 5
  2. 1*
  3.  
  4. {3                        C-ohjelmointikurssi - Osa 4
  5. {3                        ---------------------------
  6.  
  7.                            Ville-Pertti Keinonen
  8.  
  9.  
  10. Jälleen kerran kurssin osien välille jäi tarpeettoman pitkä väli. Toivotta-
  11. vasti kurssin tässä vaiheessa kaikki kurssia seuranneet pystyvät jo kuiten-
  12. kin hyödyntämään aiemmissa osissa läpi käytyjä  asioita  ja  ottamaan  itse
  13. selvää enemmän standarditoiminnoista, joita ei tässä  valitettavasti  käydä
  14. kuitenkaan enempää läpi, vaikka näin oli alunperin tarkoitus  tehdä.  Kuten
  15. kolmannessa osassa mainittiin, useimpien kääntäjien  mukana  tulee  ohjeita
  16. näiden toimintojen käyttöä varten.
  17.  
  18.  
  19. {3Tiedon käsittelytekniikkaa
  20. {3--------------------------
  21.  
  22. Yksi asioista, joita ohjelmoijien tulisi oppia voidakseen hyödyntää  tieto-
  23. konettaan tehokkaasti on se, miten ohjelmissa tulisi käsitellä tietoa.  Sen
  24. tietäminen, miten muuttujia, tiedostoja ja muistia  voidaan  käsitellä,  ei
  25. sinänsä riitä; on myös osattava ohjelmaa käytännössä kirjoittaessa päättää,
  26. millaisessa  muodossa  ja  missä  ohjelman   käsittelemät   tiedot   tulisi
  27. säilyttää.
  28.  
  29. Kaikki ohjelmat käsittelevät tietoa, riippumatta siitä, mikä niiden  varsi-
  30. nainen tarkoitus on. Silloin, kun itse tiedon käsittely ei  ole  pääasiana,
  31. on tiedon määrä useimmiten  suhteellisen  pieni,  eikä  tieto  sinänsä  ole
  32. oleellista muuta kuin ohjelman  ajon  aikana.  Tällöin  tietoa  säilytetään
  33. yleensä koneen muistissa. (Useilla ohjelmilla tosin on asetustietoja, joita
  34. tallennetaan massamuistiin.) Useimpien interaktiivisten ohjelmien runko-osa
  35. on  tällainen.  Jos  otamme  esimerkiksi  terminaaliohjelman,  sen   täytyy
  36. säilyttää mm. seuraavista asioista tietoja  (suuri  osa  näistä  on  yleis-
  37. tettävissä muihinkiin ohjelmiin):
  38.  
  39. - Käyttöliittymä (avoinna olevat ikkunat, niiden sisältö),  tarkempi  muoto
  40. riippuu yleensä käyttöjärjestelmästä.
  41.  
  42. - Laitteen ohjaus- ja  puskurointitiedot  (käyttöjärjestelmästä  riippuvia,
  43. kuten edellisessä kohdassa).
  44.  
  45. - Käyttäjän asetukset (modeemiasetukset,  hakemistopolut,  "puhelinmuistio"
  46. yms.). Ajon aikana nämä ovat muistissa, muulloin niitä säilytetään  kovale-
  47. vyllä.
  48.  
  49. - Terminaaliemulaation tila (aivan yksinkertaiset asiat, kuten kursorin si-
  50. jainti, nykyinen piirtoväri yms.), mikäli se hoidetaan itse. (Amigassa voi-
  51. daan vaihtoehtoisesti käyttää esimerkiksi laitetta  "console.device",  jol-
  52. loin itse tarvitsee huolehtia  ainoastaan  tiedon  välityksestä  laitteiden
  53. välillä.)
  54.  
  55. - Ohjelman tila. (Yksinkertaisimmillaan tämä riippuu  siitä,  missä  kohtaa
  56. ohjelmaa prosessorin suoritus kulkee, mutta mikäli  ohjelman  on  tarkoitus
  57. antaa käyttäjän tehdä useita asioita  "samanaikaisesti"  -  mitä  kutsutaan
  58. usein virheellisesti "sisäiseksi moniajoksi" -  täytyy  ohjelman  "tietää",
  59. mitä se on tekemässä.)
  60.  
  61. Näiden tietojen yksinkertaisuuden ansiosta useimmat niistä ovat hyvin  hel-
  62. posti säilytettäviä, koska niiden määrä on vakio. Riippuen toteutustavasta,
  63. käytetään lähinnä puhelinnumerolistaa sekä mahdollisesti ohjelman tila-  ja
  64. käyttöliittymätietoja varten linkattuja listoja, joista lisää hetken kulut-
  65. tua.
  66.  
  67. Sellaisissa ohjelmissa tai ohjelman osissa, joiden varsinaisena  tarkoituk-
  68. sena on käsitellä suurempia määriä tietoa, voidaan tieto säilyttää kokonaan
  69. muistissa, kokonaan  levyllä  tiedostossa  (ainoastaan  välittömästi  käsi-
  70. teltävä tiedon yksikkö on muistissa, kuten kurssin edellisen osan  esimerk-
  71. kiohjelmassa) tai osittain  kummassakin.  Yksi  oleellisimmista  tekijöistä
  72. tässä on ero sen välillä, millaisessa muodossa tieto on levyllä ja muistis-
  73. sa. Kurssin edellisen osan esimerkkiohjelmassa tieto oli täsmälleen samassa
  74. muodossa molemmissa, mikä ei kuitenkaan aina ole hyvä. Yleensä silloin, kun
  75. muistissa voi olla vaihteleva määrä  tietoa,  tietoon  liittyy  osoittimia,
  76. joita ei voida tallentaa levylle, koska samat tiedot eivät päädy  aina  sa-
  77. moihin osoitteisiin.  (Poikkeuksena  ovat  vanhanaikaisemmat  järjestelmät,
  78. jotka eivät moniaja ja joissa ohjelmat ja tiedot ladataan yleensä  kiintei-
  79. siin osoitteisiin.) Vaihtelevaa tietomäärää säilytetään yleensä linkatuissa
  80. listoissa. Linkattu lista tarkoittaa sitä, että yhdessä tiedon yksikössä on
  81. osoitin seuraavaan (ja mahdollisesti edelliseen, joka  mahdollistaa  alkion
  82. poistamisen kahden muun alkion välistä käymättä listaa läpi siihen  kohtaan
  83. asti). 
  84.  
  85. /*
  86.  * Esimerkkiohjelma linkattujen listojen käytöstä ja tiedon
  87.  * lataamisesta/tallentamisesta. Ohjelma on hieman rivieditorin
  88.  * kaltainen, mutta yksinkertaisempi ja selkeäkäyttöisempi.
  89.  * Suuremman rivimäärän käsittelemisessä tällainen on myös hidas.
  90.  */
  91.  
  92. #include <stdlib.h>
  93. #include <stdio.h>
  94. #include <string.h>
  95.  
  96. /*
  97.  * Linkatussa listassa käytettävä structure-tyyppi. Koska emme
  98.  * löydä alkioita muilla tavoin kuin yksinkertaisen linkin
  99.  * kautta, emme tarvitse osoittimia molempiin suuntiin, koska
  100.  * poistossa joudutaan käymään listaa läpi jo poistettavan
  101.  * alkion löytämistä varten.
  102.  */
  103.  
  104. struct node {
  105.   struct node *next; /* Osoitin seuraavaan alkioon. */
  106.   char *string; /* Osoitin merkkijonoon, joka on alkion sisältö. */
  107. };
  108.  
  109. /*
  110.  * Osoittimet listan ensimmäiseen ja viimeiseen alkioon.
  111.  * Osoitin ensimmäiseen edustaa periaatteessa koko listaa,
  112.  * osoitin viimeiseen ainoastaan nopeuttaa alkioiden
  113.  * lisäämistä listan loppuun. Huomaa, että molemmat ovat
  114.  * ohjelman ajoa aloitettaessa arvoltaan NULL.
  115.  */
  116.  
  117. struct node *firstnode, *lastnode;
  118.  
  119. /* Ohjelman osien prototyypit. */
  120.  
  121. struct node *getnodenr(int);
  122. struct node *addnode(struct node *, const char *);
  123. void freelist(void);
  124. void listall(void);
  125. void printnode(void);
  126. void removenode(void);
  127. void findtext(void);
  128. void enternew(void);
  129. void savelist(void);
  130. void insertfile(void);
  131.  
  132. /* Makro, jolla voidaan hypätä yli rivinvaihto stdin:stä. */
  133.  
  134. #define skiplf() \
  135.   do { \
  136.     int c; \
  137.     if ((c = getc(stdin)) != '\n') \
  138.       ungetc(c, stdin); \
  139.   } while (0)
  140.  
  141. int main(int ac, char **av)
  142. {{
  143.   /*
  144.    * Ohjelmassa on yksinkertaisuuden vuoksi samankaltainen
  145.    * tehoton käyttöliittymä kuin edellisen osan
  146.    * esimerkkiohjelmassa.
  147.    */
  148.  
  149.   for (;;) {
  150.     char cmd[2];
  151.  
  152.     puts("\nValitse toiminto:\n");
  153.     puts("l  luettele (tulosta) kaikki rivit");
  154.     puts("t  tulosta rivi");
  155.     puts("p  poista rivi");
  156.     puts("e  etsi tekstiä");
  157.     puts("k  kirjoita uusi rivi");
  158.     puts("y  tyhjennä rivimuisti");
  159.     puts("a  tallenna tiedostoon");
  160.     puts("i  lisää tiedoston sisältö rivimuistiin");
  161.     puts("u  poistu (ulos) ohjelmasta\n");
  162.  
  163.     scanf("%1s", cmd);
  164.  
  165.     switch (cmd[0]) {
  166.       case 'L':
  167.       case 'l':
  168.         listall();
  169.         break;
  170.       case 'T':
  171.       case 't':
  172.         printnode();
  173.         break;
  174.       case 'P':
  175.       case 'p':
  176.         removenode();
  177.         break;
  178.       case 'E':
  179.       case 'e':
  180.         findtext();
  181.         break;
  182.       case 'K':
  183.       case 'k':
  184.         enternew();
  185.         break;
  186.       case 'Y':
  187.       case 'y':
  188.         freelist();
  189.         puts("\nRivimuisti tyhjennetty");
  190.         break;
  191.       case 'A':
  192.       case 'a':
  193.         savelist();
  194.         break;
  195.       case 'I':
  196.       case 'i':
  197.         insertfile();
  198.         break;
  199.       case 'U':
  200.       case 'u':
  201.         freelist();
  202.         return 0;
  203.       default:
  204.         printf("\nTuntematon toiminto '%c'\n", cmd[0]);
  205.         break;
  206.     }
  207.   }
  208.  
  209.   return 0;
  210. }
  211.  
  212. /* Alkion etsintä rivinumeron perusteella. */
  213.  
  214. struct node *getnodenr(int n)
  215. {{
  216.   /*
  217.    * Muka osoitin edellistä ensimmäiseen alkioon. Koska kenttä
  218.    * "next" sijaitsee structuren alussa, se voidaan muka lukea
  219.    * tällaisen osoittimen kautta. Rivinumero nolla palauttaa
  220.    * tällöin tämän osoittimen, jota voidaan käyttää esimerkiksi
  221.    * rivien poistamisen yhteydessä.
  222.    */
  223.   struct node *p = (struct node *)&firstnode;
  224.  
  225.   while (n--) {
  226.     if (!(p = p->next))
  227.       break;
  228.   }
  229.  
  230.   return p;
  231. }
  232.  
  233. /*
  234.  * Alkion lisäämistoiminto. Parametreiksi annetaan osoitin
  235.  * edelliseen alkioon ja merkkijono, joka tulee uuden alkion
  236.  * tietosisällöksi.
  237.  */
  238.  
  239. struct node *addnode(struct node *prev, const char *string)
  240. {{
  241.   struct node *node;
  242.  
  243.   /* Varataan ensin muistista uusi alkio. */
  244.  
  245.   if (node = malloc(sizeof *node)) {
  246.     /*
  247.      * Varataan muistista kopio merkkijonosta edustamaan
  248.      * tietosisältöä.
  249.      */
  250.  
  251.     if (node->string = strdup(string)) {
  252.       /*
  253.        * Lisätään uusi alkio edellisen perään tai listan
  254.        * alkuun, mikäli edellinen on NULL-osoitin.
  255.        */
  256.  
  257.       if (prev) {
  258.         node->next = prev->next;
  259.         prev->next = node;
  260.       } else {
  261.         node->next = NULL;
  262.         firstnode = node;
  263.       }
  264.  
  265.       /*
  266.        * Mikäli alkio lisättiin listan viimeisen alkion
  267.        * perään, on se nyt viimeinen listassa. Jos lista
  268.        * on tyhjä, niin sekä prev että lastnode ovat NULL,
  269.        * joten tämä toimii siinäkin tapauksessa.
  270.        */
  271.  
  272.       if (prev == lastnode)
  273.         lastnode = node;
  274.  
  275.       /*
  276.        * Palataan tässä aliohjelmasta, sillä loppuosa on
  277.        * tarkoitettu ajettavaksi siinä tapauksessa, että
  278.        * muistin varaaminen epäonnistui. Palautetaan
  279.        * osoitin uuteen alkioon.
  280.        */
  281.  
  282.       return node;
  283.     }
  284.  
  285.     free(node);
  286.   }
  287.  
  288.   printf("Varoitus: Uutta alkiota ei voitu luoda\n");
  289.  
  290.   return NULL;
  291. }
  292.  
  293. /* Aliohjelma, joka vapauttaa koko listan. */
  294.  
  295. void freelist(void)
  296. {{
  297.   struct node *node, *next;
  298.  
  299.   for (node = firstnode; node; node = next) {
  300.     next = node->next;
  301.     free(node);
  302.   }
  303.  
  304.   firstnode = NULL;
  305.   lastnode = NULL;
  306. }
  307.  
  308. void listall(void)
  309. {{
  310.   struct node *node;
  311.   int n;
  312.  
  313.   puts("\nMuistissa olevat rivit:\n");
  314.  
  315.   for (n = 1, node = firstnode; node; ++n, node = node->next)
  316.     printf("%d: %s\n", n, node->string);
  317. }
  318.  
  319. void printnode(void)
  320. {{
  321.   struct node *node = NULL;
  322.   int n;
  323.  
  324.   puts("\nAnna tulostettavan rivin numero");
  325.   scanf("%d", &n);
  326.  
  327.   if (n)
  328.     node = getnodenr(n);
  329.  
  330.   if (node)
  331.     printf("\n%d: %s\n", n, node->string);
  332.   else
  333.     printf("\nRiviä numero %d ei ole.\n", n);
  334. }
  335.  
  336. void removenode(void)
  337. {{
  338.   struct node *p = NULL, *node;
  339.   int n;
  340.  
  341.   puts("\nAnna poistettavan rivin numero");
  342.   scanf("%d", &n);
  343.  
  344.   if (n)
  345.     p = getnodenr(n - 1);
  346.  
  347.   /*
  348.    * p osoittaa edelliseen, joten sillä täytyy olla kenttänä
  349.    * myös osoitin seuraavaan.
  350.    */
  351.  
  352.   if (p && (node = p->next)) {
  353.  
  354.     p->next = node->next;
  355.  
  356.     /* Jos node oli viimeinen, se on nyt p. */
  357.  
  358.     if (node == lastnode)
  359.       lastnode = p;
  360.  
  361.     free(node->string);
  362.     free(node);
  363.   } else
  364.     printf("\nRiviä numero %d ei ole.\n", n);
  365. }
  366.  
  367. void findtext(void)
  368. {{
  369.   struct node *node;
  370.   int n, found = 0;
  371.   size_t len, slen;
  372.   char buf[256];
  373.   char *s;
  374.  
  375.   puts("\nAnna etsittävä merkkijono");
  376.   skiplf();
  377.   gets(buf);
  378.  
  379.   len = strlen(buf);
  380.  
  381.   if (len && buf[len - 1] == '\n')
  382.     --len;
  383.  
  384.   /* Ei etsitä tyhjää merkkijonoa. */
  385.  
  386.   if (!len)
  387.     return;
  388.  
  389.   /* Käytetään hyvin yksinkertaista etsintämenetelmää. */
  390.  
  391.   for (n = 1, node = firstnode; node; ++n, node = node->next) {
  392.     s = node->string;
  393.     slen = strlen(s);
  394.  
  395.     while (*s && slen-- >= len) {
  396.       if (!strnicmp(buf, s++, len)) {
  397.         ++found;
  398.         printf("%d: %s\n", n, node->string);
  399.         break;
  400.       }
  401.     }
  402.   }
  403.  
  404.   printf("\nMerkkijono löytyi %d riviltä.\n", found);
  405. }
  406.  
  407. /* Uuden rivin lisäämistoiminto. */
  408.  
  409. void enternew(void)
  410. {{
  411.   char buf[256];
  412.   size_t i;
  413.  
  414.   puts("\nKirjoita uusi rivi");
  415.   skiplf();
  416.   gets(buf);
  417.  
  418.   i = strlen(buf);
  419.  
  420.   if (i && buf[i - 1] == '\n')
  421.     --i;
  422.  
  423.   buf[i] = '\0';
  424.  
  425.   addnode(lastnode, buf);
  426. }
  427.  
  428. /* Rivimuistin tallentamistoiminto. */
  429.  
  430. void savelist(void)
  431. {{
  432.   struct node *node;
  433.   char filename[32];
  434.   FILE *fp;
  435.   int i;
  436.  
  437.   puts("\nAnna tiedoston nimi, johon tallennetaan");
  438.   scanf("%31s", filename);
  439.  
  440.   if (fp = fopen(filename, "w")) {
  441.     for (i = 0, node = firstnode; node; ++i, node = node->next)
  442.       fprintf(fp, "%s\n", node->string);
  443.     fclose(fp);
  444.     printf("\nTiedostoon kirjoitettiin %d riviä\n", i);
  445.   } else
  446.     printf("\nTiedostoa \"%s\" ei voitu luoda\n", filename);
  447. }
  448.  
  449. /* Tiedoston lisääminen. */
  450.  
  451. void insertfile(void)
  452. {{
  453.   char filename[32];
  454.   struct node *p;
  455.   char buf[256];
  456.   FILE *fp;
  457.   int n, i;
  458.  
  459.   puts("\nAnna lisättävän tiedoston nimi");
  460.   scanf("%31s", filename);
  461.  
  462.   puts("\nAnna rivinumero, jonka perään lisätään");
  463.   scanf("%d", &n);
  464.  
  465.   p = getnodenr(n);
  466.  
  467.   if (p) {
  468.     if (fp = fopen(filename, "r")) {
  469.       for (i = 0; fgets(buf, sizeof buf, fp); ) {
  470.         char *s = buf + strlen(buf) - 1;
  471.  
  472.         if (*s == '\n')
  473.           *s-- = '\0';
  474.  
  475.         /* Tyhjiä rivejä ei lisätä. */
  476.  
  477.         if (s == buf)
  478.           continue;
  479.  
  480.         /* Jos lisääminen epäonnistuu, lopetetaan. */
  481.  
  482.         if (!(p = addnode(p, buf)))
  483.           break;
  484.  
  485.         ++i;
  486.       }
  487.  
  488.       fclose(fp);
  489.  
  490.       printf("Tiedostosta lisättiin %d riviä.\n", i);
  491.     }
  492.   } else
  493.     printf("\nRiviä %d ei ole.\n", n);
  494. }
  495.  
  496. Ohjelman pitäisi muiden esimerkkiohjelmien tavoin kääntyä melkein millä ta-
  497. hansa kääntäjällä. Käyttö lienee varsin selvää.
  498.  
  499. Edellinen ohjelma  oli  esimerkkinä  interaktiivisista  ohjelmista,  joilla
  500. käyttäjä voi käsitellä tietoa. Usein ohjelmat saattavat prosessoida  suuren
  501. määrän tietoa säännönmukaisesti, ilman käyttäjän vaikutusta asiaan. Tällai-
  502. nen on esimerkiksi C-kääntäjä. Tämäntyyppisissä ohjelmissa on tiedon käsit-
  503. telyn nopeus erityisen tärkeää ja sen vuoksi käytetään erityisiä tekniikoi-
  504. ta, joilla voidaan  nopeuttaa  toimintaa  yksinkertaisempaan  toteutusmene-
  505. telmään verrattuna.
  506.  
  507. Hyvä esimerkki tällaisesta nopeutustekniikasta  on  listojen  "hashaaminen"
  508. (en tiedä oikeata suomenkielistä  termiä)  eli  se,  että  lista  joistakin
  509. asioista jaetaan useampaan listaan jonkin sellaisen perusteella, joka jakaa
  510. ne melko tasaisesti näihin listoihin ja nopeuttaa  etsintää  mahdollisimman
  511. paljon. C-kääntäjässä tällaista  käytetään  mm.  symbolien  säilyttämiseen.
  512. Listan numero (hash-arvo) voidaan laskea esimerkiksi symbolin  nimen  merk-
  513. kien ASCII-arvojen summasta. Suoraan ensimmäisen merkin  käyttäminen  hash-
  514. arvona ei olisi kovin tehokasta, koska arvot eivät olisi tasaisia ja  merk-
  515. kijonojen vertailu pääsisi aina hiukan pitemmälle merkkijonossa kuin täysin
  516. erilaisen tapauksessa. Yksinkertaistettuna symbolitoiminnot  voisivat  olla
  517. esimerkiksi seuraavanlaisia (tässä puuttuvat symbolien varsinaiset tiedot):
  518.  
  519. /*
  520.  * Käytetään kahden potenssia hash-taulukon kokona, jotta
  521.  * voitaisiin käyttää nopeaa '&'-toimintoa laskennassa.
  522.  */
  523.  
  524. #define SYMHASH 256
  525.  
  526. struct symbol {
  527.   struct symbol *next;
  528.   /*
  529.    * Varaamalla symbolin tunnuksen (nimen) suoraan alkion
  530.    * perään säästytään useammalta muistinvarauskutsulta.
  531.    */
  532.   char id[1];
  533. };
  534.  
  535. struct symbol symtab[SYMHASH];
  536.  
  537. int calchash(const char *s)
  538. {{
  539.   int h = 0, c;
  540.  
  541.   while (c = (unsigned char)*s++)
  542.     h += c;
  543.  
  544.   return h & (SYMHASH - 1);
  545. }
  546.  
  547. struct symbol *addsym(const char *id)
  548. {{
  549.   struct symbol *sym;
  550.   int h = calchash(id);
  551.  
  552.   sym = getmem(sizeof *sym + strlen(id));
  553.  
  554.   strcpy(sym->id, id);
  555.  
  556.   sym->next = symtab[h];
  557.   symtab[h] = sym;
  558.  
  559.   return sym;
  560. }
  561.  
  562. struct symbol *getsym(const char *id)
  563. {{
  564.   struct symbol *sym;
  565.   int h = calchash(id);
  566.  
  567.   for (sym = symtab[h]; sym; sym = sym->next) {
  568.     if (!strcmp(sym->id, id))
  569.       break;
  570.   }
  571.  
  572.   return sym;
  573. }
  574.  
  575. /*
  576.  * Muistin varaamiseen käytetään normaalia malloc():ia
  577.  * nopeampaa ja suurella määrällä varauksia vähemmän
  578.  * muistia kuluttavaa toimintoa, joka lisäksi poistuu
  579.  * ohjelmasta, mikäli varaus epäonnistuu.
  580.  */
  581.  
  582. char *memptr;
  583. size_t memleft;
  584.  
  585. #define CHUNKSIZE 65536
  586.  
  587. void *getmem(size_t bytes)
  588. {{
  589.   void *p;
  590.  
  591.   /* Varmistetaan, että tavumäärä on parillinen. */
  592.  
  593.   bytes = (bytes + 1) & ~1;
  594.  
  595.   if (bytes > CHUNKSIZE)
  596.     abort();
  597.  
  598.   /* Jos ei nykyisestä lohkosta ole jäljellä tarpeeksi,
  599.      varataan uusi ja unohdetaan nykyinen. */
  600.  
  601.   if (memleft < bytes) {
  602.     if (!(memptr = malloc(CHUNKSIZE)))
  603.       abort();
  604.     memleft = CHUNKSIZE;
  605.   }
  606.  
  607.   /* Annetaan muistilohkon seuraavat "bytes" tavua. */
  608.  
  609.   p = memptr;
  610.   memptr += bytes;
  611.   memleft -= bytes;
  612.  
  613.   return p;
  614. }
  615.  
  616. Oikeassa ohjelmassa olisi symboleilla varmasti muutakin tietoa, ja getmem()
  617. saattaisi muistaa varaamansa lohkot, jotta ne voitaisiin vapauttaa  sitten,
  618. kun symbolitaulukon nykyistä sisältöä ei enää tarvita. C-kääntäjän  tapauk-
  619. sessa symbolitaulukkoja olisi lisäksi useampia, koska jokaisen  ohjelmaloh-
  620. kon sisällä voi olla  paikallisia  symboleita.  Jokaiselle  lohkolle  olisi
  621. tällöin myös omat listansa muistilohkoja, joista  varattaisiin  kaikki  sen
  622. lohkon paikalliset tiedot. Käytettyjen tekniikoiden toiminnan pitäisi  kui-
  623. tenkin selvitä ylläolevista esimerkkitoiminnoista.
  624.  
  625.  
  626. {3Käännöksen vaiheet, C-kääntäjän toiminta
  627. {3----------------------------------------
  628.  
  629. Kuten aiemmissa osissa on mainittu, C-ohjelma käännetään useassa vaiheessa.
  630. Näiden vaiheiden samoin kuin C-kääntäjän toiminnan  tietäminen  on  hyödyl-
  631. listä. Vaiheet on tässä selostettu lyhyesti  sellaisina,  kuin  ne  yleensä
  632. ovat - monet kääntäjät saattavat kuitenkin yhdistää näitä vaiheita tai mah-
  633. dollisesti jakaa niitä edelleen. Sisäisesti kaikki kääntäjät toimivat  kui-
  634. tenkin ainakin käännöksen alkuvaiheissa suurin piirtein samalla tavoin.
  635.  
  636. Ensimmäinen vaihe käännöksessä on esikäsittely, jonka  ohjaamiseen  käytet-
  637. tyjä komentoja ja sen suorittamia asioita käsiteltiin pääpiirteiltään kurs-
  638. sin toisessa osassa. Esikäsittelijäohjelma on  standardilta  nimitykseltään
  639. "cpp" (luultavasti tulee sanoista "C preprocessor"), joissakin  kääntäjissä
  640. tämä saattaa  olla  muunnettuna  (esimerkiksi  DICE:n  "cpp"  on  nimeltään
  641. "dcpp"). Joskus tämä vaihe saatetaan ajaa manuaalisesti (yleensä tosin etu-
  642. liittymän option avulla), jos käsittelyä käytetään vaikka  muiden  tekstien
  643. kuin C-lähdekoodien käsittelemiseen. (Esimerkiksi "Makefile"-tiedostojen ja
  644. assemblysorsien käsittely on yleistä Unix-systeemeissä.)
  645.  
  646. Esikäännösvaiheessa C-sorsa käydään nopeasti läpi. Ainoastaan  '#'-merkillä
  647. alkavat rivit tulkitaan, kaikki muu ainoastaan selataan  läpi  siten,  että
  648. kommentit poistetaan ja korvataan tyhjällä ja itse koodisisältö käydään si-
  649. ten  läpi,  että  se  eritellään  yhtenäisiksi  sanoiksi,  joista  symbolin
  650. näköisiä sanoja verrataan makrolistaan ja tarpeen vaatiessa korvataan  sym-
  651. bolia vastaavalla makrolla. Lopputuloksena on yleensä  alkuperäistä  sorsaa
  652. pitempi (include-tiedostojen ansiosta, jotka luetaan suoraan mukaan sorsaan
  653. siihen kohtaan, missä #include-rivi oli) tiedosto, jossa ei ole kommentteja
  654. eikä yleensä muita '#'-rivejä kuin #line-rivejä, jotka kertovat seuraavalle
  655. käännösvaiheelle missä mennään, jotta virheiden  kohdat  voidaan  ilmoittaa
  656. oikein. Vaihdellen kääntäjän mukaan, saatetaan käyttää muitakin '#'-rivejä;
  657. DICE jättää mm. #pragma-komennot ja lisää mahdollisen nopeutetun  esikäsit-
  658. telyoption yhteydessä viitteen valmiiksi puolikäännettyyn tiedostoon.  Ami-
  659. gan kääntäjissä tämä on yleistä, koska Amigan käyttöjärjestelmän  toiminto-
  660. jen hyödyntämistä varten tarvitsee kohtuuttoman suuren määrän  include-tie-
  661. dostoja, joiden tulkinta kestää kauan.
  662.  
  663. Seuraava käännöksen vaihe on yleensä varsinainen kääntäminen, joka  koostuu
  664. itsessään useammista vaiheista. Tämän vaiheen hoitaa yleensä  "cc1"-niminen
  665. ohjelma (DICE:n vastaava on "dc1"), jota ei juuri missään  yhteydessä  tar-
  666. vitse ajaa manuaalisesti.
  667.  
  668. Varsinaisen käännöksen ensimmäinen vaihe  koostuu  kahdesta  "kerroksesta",
  669. leksikaalisesta analyysistä ja koodin parseauksesta. Leksikaalinen analyysi
  670. lukee esikäsiteltyä tiedostoa ja jakaa sitä  leksikaalisiin  elementteihin.
  671. Näiden elementtien välillä voi olla vapaasti välejä ja tyhjiä rivejä, jotka
  672. eivät itsessään kuulu näihin elementteihin, koska ne eivät tarkoita mitään;
  673. tämän ansiota on C-kielen vapaamuotoisuus. Tämä vaihe  käännöksestä  saate-
  674. taan usein hoitaa automaattisesti generoidulla "lekserillä". (Näitä voidaan
  675. generoida mm. ohjelmilla lex ja flex.)
  676.  
  677. Leksikaalisen analyysin elementit annetaan eteenpäin parserille, joka  tul-
  678. kitsee niiden merkityksen (lekseri ei yleensä  havaitse  virheitä  ohjelman
  679. muodossa, sen sijaan parseri havaitsee heti, jos elementeistä  ei  kokonai-
  680. suutena muodostu mitään järkevää) ja rakentaa sen perusteella muistiin sym-
  681. bolitaulukoita,  tyyppimääritelmiä  ja  väliaikaisia   rakenteita,   joista
  682. myöhemmin generoidaan itse koodi. Koodia voi periaatteessa  generoida  mel-
  683. kein suoraan parserista, mutta tällöin optimointimahdollisuudet jäävät var-
  684. sin rajalliseksi. Vanha Aztec C vaikutti generoivan  koodia  melkein  tällä
  685. tavoin, nykyisistä kääntäjistä  DICE  vaikuttaa  sen  tuottamasta  koodista
  686. päätellen miltei näin yksinkertaiselta. Parserit  generoidaan  usein  auto-
  687. maattisesti mm. yacc-, byacc- ja bison-ohjelmilla.
  688.  
  689. Kääntäjästä riippuen voi vaihdella suuresti,  miten  monen  vaiheen  kautta
  690. varsinainen koodi tuotetaan. Optimoivissa kääntäjissä yleensä näitä vaihei-
  691. ta on paljon ja ne kestävät aikansa ja vievät paljon muistia, koska  ohjel-
  692. man jokin osa on kokonaisuudessaan muistissa, kun sitä käsitellään. GNU C:n
  693. tapauksessa koodia tuotetaan yksi funktio kerralla, joten itse  lähdekoodi-
  694. tiedoston koko ei juurikaan vaikuta muistinkulutukseen.
  695.  
  696. Ohjelmointikielten kääntämisen tarkemmasta toteutuksesta kiinnostuneiden on
  697. syytä hankkia käsiinsä flex ja bison (molemmat tulevat GNU C:n  mukana)  ja
  698. lukea näiden ohjeet.
  699.  
  700. Varsinaisen käännöksen lopputuloksena on yleensä assemblykielinen lähdekoo-
  701. ditiedosto, jotkut kääntäjät tosin saattavat tuottaa suoraan objektikoodia.
  702. Useimpien kääntäjien etuliittymässä on mahdollisuus  käännöksen  lopettami-
  703. seen tässä vaiheessa, jolloin tuotettua  assemblykoodia  voi  tutkia  itse.
  704. Jotkut ohjelmat saattavat myös muutella koodia tässä vaiheessa. (Itse  tein
  705. aikoinaan DICE:en  ulkoisen  optimointilisävaiheen,  joka  muokkasi  koodia
  706. tässä vaiheessa.)
  707.  
  708. Seuraava vaihe on assemblysorsan kääntäminen objektikoodiksi. Tämä on nopea
  709. vaihe, sillä erityisesti C-kääntäjän tuottama assemblerkoodi  on  jo  hyvin
  710. samankaltaista itse  ajettavan  koodin  kanssa.  Eri  C-kääntäjien  assemb-
  711. lerkääntäjät  vaihtelevat  hyvin  paljon.  DICE:n  "das"   on   normaalista
  712. kääntäjästä pelkistetty versio, joka osaa vain  tarkoitukseen  tarpeelliset
  713. asiat. Sen sijaan GNU C:n assemblerkääntäjä "as" on erittäin  monipuolinen,
  714. vaikkakaan se ei muistuta syntaksiltaan Amigan kääntäjiä, sillä se  käyttää
  715. mielestäni parempaa MIT-syntaksia Amigalla tavanomaisemman Motorola-syntak-
  716. sin sijaan.  Se  tukee  tosin  osittain  myös  Motorola-syntaksia.  Assemb-
  717. lerkäännöksen tuloksena olevien objektikooditiedostojen päätteenä on  ".o".
  718. Nämä objektikooditiedostot sisältävät periaatteessa valmista ajettavaa koo-
  719. dia, mutta ne sisältävät symboliviitteitä, joita ei ole vielä  paikannettu.
  720. koska kyseiset symbolit sijaitsevat eri objektitiedostoissa  tai  mahdolli-
  721. sesti linkattavissa funktiokirjastoissa. Objektikooditiedostoja voidaan jo-
  722. ko kerätä yhteen funktiokirjastoiksi tai linkata ajettaviksi ohjelmiksi.
  723.  
  724. Linkkaamisvaiheessa useampia objektikooditiedostoja sekä  funktiokirjastoja
  725. (standardit C-toiminnot sijaitsevat  tällaisessa)  yhdistetään  ajettavaksi
  726. ohjelmaksi.  Ero  objektikooditiedostojen  ja  kirjastojen   käsittelemisen
  727. välillä on se, että objektikooditiedostot sisällytetään yleensä kaikki  oh-
  728. jelmaan, mutta kirjastoista valitaan ainoastaan ne kohdat (entiset objekti-
  729. kooditiedostot), jotka sisältävät sellaisia symboleita, joihin on viitteitä
  730. itse ohjelman objektikooditiedostoissa. Linkkaamiseen käytetty  ohjelma  on
  731. yleiseltä nimeltään "ld" (DICE:n linkkerin nimi on "dlink").
  732.  
  733. Normaalisti etuliittymä hoitaa nämä kaikki vaiheet  tai  valikoidusti  vain
  734. tarpeelliset vaiheet. Yleensä etuliittymä tunnistaa sille parametreina  an-
  735. nettujen ohjelmien muodot niiden päätteistä ja ajaa ne  tarpeellisten  vai-
  736. heiden läpi. Etuliittymälle voidaan antaa  myös  optioita,  jotka  kertovat
  737. sille, mihin vaiheeseen asti olisi  käännettävä.  Tyypillisesti  suurempien
  738. ohjelmien tapauksessa C-sorsat käännetään ensin objektikoodiksi, ja  sitten
  739. annetaan joko etuliittymälle tai suoraan linkkerille kerralla kaikki objek-
  740. tikoodit, jotta niistä linkattaisiin ajettava ohjelma.
  741.  
  742.  
  743. {3Suuren projektin kasassapito
  744. {3----------------------------
  745.  
  746. Edellä kerrottuja tietoja voidaan  hyödyntää  erityisesti  suuria  ohjelmia
  747. tehdessä. Tässä on muutamia vihjeitä siitä, mitä kannattaa tehdä jos ohjel-
  748. ma on vähänkin suurempi kuin ei mitään tai koostuu useista ohjelmista.
  749.  
  750. Aina kannattaa jakaa  ohjelmat  useisiin  lähdekooditiedostoihin.  Ohjelman
  751. käyttämät makrot ja tyyppimäärittelyt kannattaa tehdä include-tiedostoihin,
  752. jotka liitetään #include-rivien avulla eri  lähdekooditiedostoihin.  Lähde-
  753. kooditiedostot kannattaa jakaa loogisesti siten, että tiedoston nimi kuvaa,
  754. minkätyyppisiä funktioita se sisältää. (Lähdekooditiedostojen niminä  saat-
  755. taisi olla esimerkiksi "main.c", "init.c", "window.c", "arexx.c" jne.)  Ja-
  756. kamalla loogisesti toiminnot tiedostoihin  päätyvät  globaaliset  muuttujat
  757. usein oikeisiin tiedostoihin siten, että jotakin muuttujaa käyttävät  funk-
  758. tiot ovat samassa tiedostossa. Sellaiset muuttujat, joita tarvitaan  useam-
  759. missa lähdekooditiedostossa, täytyy tietysti muissa kuin  määrittelytiedos-
  760. tossa määritellä "extern":ksi. Usein on hyvä ratkaisu laittaa tällaiset ex-
  761. tern-määrittelyt sekä funktioprototyypit johonkin include-tiedostoon.
  762.  
  763. Ohjelmille kannattaa aina antaa versionumerot. (Näitä ei tarvinne  selittää
  764. sen enempää, koska kaikki ovat varmasti nähneet niitä  muissa  ohjelmissa.)
  765. Versionumero sekä kaikki merkkijonot, jotka sisältävät sen, kannattaa lait-
  766. taa yhteen lähdekooditiedostoon, joka on nopea kääntää mikäli se ei sisällä
  767. muuta. Kehittyneempää ohjelmien versioiden seuraamista varten  on  olemassa
  768. valmiita ohjelmia, jotka pitävät lukua lähdekoodikohtaisista versioista  ja
  769. kaikista koodille tehdyistä muutoksista. Tällainen on esimerkiksi RCS  (Re-
  770. vision Control System), josta lötyy Amigalle portattu hieman muuteltu  ver-
  771. sio HWGRCS tai jonka voi GNU  C:llä  kääntää  itse.  (Alkuperäinen  paketti
  772. löytyy useimmista ftp-siteistä /pub/gnu-hakemistosta, nykyinen taitaa  olla
  773. rcs-5.7.tar.gz.)
  774.  
  775. RCS:lle on myös käyttöä helpottava ja muita ominaisuuksia lisäävä  CVS-etu-
  776. liittymä (Concurrent Versions System), jonka versiosta 1.3 löytyy  Amigalle
  777. portattu  versio.  Nykyinen  versio  on  tosin  jo  1.6,  paketin  nimi  on
  778. cvs-1.6.tar.gz. Molemmista näistä ohjelmista on erityisesti silloin hyötyä,
  779. kun samaa ohjelmaa kehittävät useat ihmiset yhdessä. (Silloin niitä on  to-
  780. sin parempi käyttää Unix-systeemeissä, koska Amiga ei  tue  suoraan  useita
  781. käyttäjiä.) Amigalle tehtyihin ohjelmiin kannattaa laittaa  myös  sellainen
  782. versionumero, jonka Amigan  "version"-komento  osaa  lukea.  Riittää,  että
  783. laittaa johonkin kohtaan ohjelmaa merkkijonon (jolla itse ohjelman ei  tar-
  784. vitse  välttämättä  tehdä  mitään)  muodossa  "$VER:  ohjelma  versionumero
  785. (päivämäärä)", jossa päivämäärä on muodossa päivä.kuukausi.vuosi.  Jossakin
  786. ohjelmassa voisi olla esimerkiksi:
  787.  
  788. const char versionstr[] = { "$VER: myprogram 1.2 (10.6.95)" };
  789.  
  790. Tällaisia versiomerkkijonoja osaa ainakin HWGRCS pitää yllä  automaattises-
  791. ti. Niiden automaattista päivitykstä varten voi myös kirjoittaa esimerkiksi
  792. awk-ohjelmia. (gawk, eli GNU awk tulee GNU C:n mukana ja on varsin  hyödyl-
  793. linen monessa muussakin - sen käyttö  kannattaa  opetella  erityisesti  jos
  794. käyttää (vaikka ei ohjelmoisikaan) Amigan lisäksi Unix-järjestelmiä.)
  795.  
  796. Yksi hyödyllisimmistä ohjelmista, joita C-kääntäjien  kanssa  miltei  poik-
  797. keuksetta tulee, on make-apuohjelma (DICE:n make on tietysti "dmake").  Sen
  798. käyttö kannattaa ehdottomasti opetella,  koska  pitemmän  päälle  ohjelmien
  799. kääntäminen manuaalisesti on todella tuskastuttavaa, jos ohjelmaa  tehdessä
  800. ja korjatessa sitä joutuu kääntämään useaan kertaan. (Yleensä ohjelmia jou-
  801. tuu kääntämään hyvin usein.) Kun  kirjoittaa  ohjelman  kääntämistä  varten
  802. "Makefile"-tiedoston (tarkempaa tietoa tästä  saa  kääntäjän  make-ohjelman
  803. ohjeista), voi koko ohjelman kääntämisen suorittaa yksinkertaisesti  komen-
  804. nolla "make". Lisäetuna make kääntää ainoastaan  ne  ohjelman  osat,  jotka
  805. ovat muuttuneet; mikäli objektikooditiedoston päivämäärä  on  uudempi  kuin
  806. lähdekoodin, sitä ei tarvitse kääntää uudelleen. "Makefile"-tiedostoon  voi
  807. laittaa myös muita automaattisia toimintoja, kuten komentoja versionumeroi-
  808. den päivittämiseen.
  809.  
  810.  
  811.  
  812.  
  813. {3C-laajennuksia
  814. {3--------------
  815.  
  816. Tässä osassa on lueteltuna joitakin C-kielen laajennuksia, joita  tietyissä
  817. kääntäjissä on. Näistä löytyy lisää tietoa kääntäjien  ohjeista,  tässä  on
  818. vain mainittuna pari hyödyllistä laajennusta,  joita  saattaisi  olla  hyvä
  819. opetella käyttämään.
  820.  
  821. {3DICE C
  822.  
  823. DICE:ssä on ylimääräinen  automaattinen  esikääntäjämakro,  __COMMODORE_DA-
  824. TE__, joka korvautuu merkkijonolla, joka on nykyinen päivämäärä  yllämaini-
  825. tussa "$VER:"-merkkijonojen vaatimassa muodossa.
  826.  
  827. DICE:n  funktiomäärittelyissä  voi  määrätä   suoraan   rekisterit,   joita
  828. käytetään parametrien välittämiseen. Rekisteri määritellään muodossa  __re-
  829. kisteri, eli esimerkiksi __D0 tai __A0.
  830.  
  831. DICE:n  funktiomäärittelyissä  voi  käyttää  __autoinit-   ja   __autoexit-
  832. lisämääreitä, joiden avulla funktio saadaan ajettua automaattisesti  ohjel-
  833. man käynnistys/poistumisvaiheessa. Näistä on erityisesti  hyötyä  linkatta-
  834. vien funktiokirjastojen yhteydessä.
  835.  
  836. {3GNU C
  837.  
  838. GCC:n switch()-rakenteessa voidaan case-kohtiin antaa jokin lukuväli  lait-
  839. tamalla vakioiden väliin "...", esimerkiksi "case 0 ...  9:"  toteutuu  ar-
  840. voille nollasta yhdeksään.
  841.  
  842. GCC:ssä on erittäin tehokas (koska se ei rajoita  pahasti  kääntäjän  opti-
  843. mointeja),  vaikkakin  hieman  vaikeakäyttöinen   inline-assembly-toiminto,
  844. __asm__(), jolla voidaan laittaa suoraan C-lähdekoodin  sekaan  assemblyko-
  845. mentoja. Tästä voi olla hyötyä käytettäessä  hyvin  matalatasoisia  operaa-
  846. tioita (kuten pino-osoittimen muuttelua tai MMU:n ohjausta)  tai  erityisiä
  847. toimintaa nopeuttavia komentoja, joita C-kääntäjä ei osaa  suoraan  tuottaa
  848. (kuten "bfffo"). Sillä voidaan myös pakottaa muuttujia haluttuihin rekiste-
  849. reihin yms.
  850.  
  851.  
  852. {3Bugien etsintä
  853. {3--------------
  854.  
  855. Mitä suurempi ohjelma, sitä varmempaa on, että siinä on bugeja  (ohjelmoin-
  856. tivirheitä). Bugien etsintä ja korjaaminen on erittäin  tärkeä  osa  ohjel-
  857. mointia, joissakin tapauksissa siihen saattaa mennä  moninkertaisesti  niin
  858. paljon aikaa kuin itse ohjelmakoodin  kirjoittamiseen  -  erityisesti,  jos
  859. kirjoittaa ohjelman kerralla ennenkuin kokeilee sitä.
  860.  
  861. Bugit ilmenevät yleensä kahdella tavalla: ohjelma ei tee mitä  sen  pitäisi
  862. tai tekee jotain jota sen ei pitäisi tehdä - yleensä kaatuu tai kaataa koko
  863. koneen. Jälkimmäiset bugit ovat yleensä erityisen vakavia ja vaikeita  kor-
  864. jata, erityisesti kun Amigan kaltaisessa suojaamattomassa ympäristössä  oh-
  865. jelma voi sotkea muita ohjelmia ja sekoittaa  siten  koko  koneen  erittäin
  866. helposti. Koneen tahalliseen kaatamiseen riittää  vaikka  seuraavannäköinen
  867. lause:
  868.  
  869. *(void **)4 = 0;
  870.  
  871. Ylläoleva nollaa muistiosoitteen 4, jonka  pitäisi  osoittaa  exec.libraryn
  872. kantaosoitteeseen, joka ei ole nolla. Tietysti tahallinen koneen kaataminen
  873. on aina helppoa - se onnistuu jopa suojatuissa ympäristöissä,  ainakin  jos
  874. on "root"-käyttäjä: joskus kokeilin huvikseni Linuxissa shell-komentoa "yes
  875. >/dev/kmem", ja kaatuihan se - mutta Amigassa voi sotkea  muistia  helposti
  876. vahingossakin, jos jokin osoitin ei osoitakaan oikeaan osoitteeseen.
  877.  
  878. Sellaiset bugit, jotka ilmenevät säännönmukaisesti aina tietyssä tilantees-
  879. sa, ovat yleensä helposti korjattavissa olevia. Tarvitsee ainoastaan  etsiä
  880. bugin kohta, jonka voi yleensä päätellä suunnilleen jo siitä, mitä  ohjelma
  881. oli sekoamisen hetkellä tekemässä. Tarkemman kohdan voi löytää  esimerkiksi
  882. siten, että laittaa ohjelman tulostamaan  vähän  väliä  ilmoituksia  siitä,
  883. missä kohtaa ohjelmaa mennään. Myös  muuttujien  arvon  tulostus  voi  olla
  884. hyödyllistä, sillä niistä usein näkee, mikä on  pielessä,  erityisesti  jos
  885. vika on jossain kohdassa ennen bugin ilmenemiskohtaa. Tämän menetelmän  op-
  886. pii parhaiten kokemuksen myötä. Vähitellen bugeja oppii  myös  "arvaamaan";
  887. parhaimmillaan ohjelman kaatuessa ohjelmoija päättelee  lähdekoodia  katso-
  888. matta, missä vika on, tai jopa arvaa, missä luultavasti on vikaa ennen kuin
  889. edes kokeilee ohjelmaa.
  890.  
  891. Vaikka aina voidaan systemaattisesti etsiä bugeja edellämainitulla tavalla,
  892. ei se kuitenkaan yleensä ole  helppoa.  Joskus  bugeja  ei  tunnu  löytävän
  893. millään: voi esimerkiksi nähdä, että muuttujalla on aivan väärä arvo, mutta
  894. ei välttämättä ole kovinkaan selvää, mistä se on peräisin. Bugien  etsintää
  895. helpottavat huomattavasti  tähän  tarkoitukseen  suunnitellut  apuohjelmat,
  896. joita löytyy runsaasti ilmaisohjelmina. Amigalla yleisimpiä ovat  Enforcer,
  897. joka vahtii ohjelman osoittamia muistiosoitteita  (se  tosin  vaatii  MMU:n
  898. toimiakseen!) ja Mungwall, joka vahtii muistinvarauksia.  Uudempi  tulokas,
  899. jota voi käyttää Enforcerin sijasta, vaikka koneessa ei  olisi  MMU:ta,  on
  900. Apurify, josta löytyy versiot DICE:lle ja GNU C:lle (gcc:n nykyisen version
  901. (2.7.0) mukaana tulee Apurify). Näiden ja muiden debuggaustyökalujen käyttö
  902. selviää tietysti ohjeista.
  903.  
  904.  
  905. {3Amigan käyttöjärjestelmä
  906. {3------------------------
  907.  
  908. Tässä kurssissa on käsitelty C-kieltä pääasiassa yleisesti, mutta  useimmat
  909. varmasti haluavat myös oppia hyödyntämään Amigan käyttöjärjestelmää. Amigan
  910. käyttöjärjestelmän hyödyntämistä ei tässä varsinaisesti käsitellä, osittain
  911. siksi,  etten  henkilökohtaisesti  pidä  siitä,  miten  C-kieltä  on  siinä
  912. väärinkäytetty, mutta kerron kuitenkin, mitä sitä varten olisi opeteltava.
  913.  
  914. Amigan käyttöjärjestelmän toiminnot löytyvät erilaisista  kirjastoista  (ei
  915. pidä sekoittaa linkattaviin funktiokirjastoihin), jotka sisältävät valtavan
  916. määrän funktioita. Amigan käyttöjärjestelmän hyödyntämiseksi täytyy opetel-
  917. la käyttämään näitä  funktioita  ja  niihin  liittyviä  structure-tyyppejä.
  918. Näistä löytyy hiukan tietoa Amigan include-tiedostoista ja enemmän ns. "au-
  919. todoc"-tiedostoista, jotka on saatavilla elektronisessa muodossa native de-
  920. veloper kitin mukana. Painettua tietoa löytyy kirjasarjasta Amiga ROM  Ker-
  921. nel Reference Manual, jossa on autodocit ja paljon muuta tietoa Amigan  oh-
  922. jelmoinnista esimerkkeineen.
  923.  
  924. Lopuksi liitän mukaan esimerkiksi hieman siistityn (ulkomuodon  siistimisen
  925. lisäksi on lisätty mm. onnistumisen tarkistukset ja mahdollisuus keskeyttää
  926. laskenta) version ensimmäisestä varsinaisesta  C-ohjelmastani.  Se  piirtää
  927. Mandelbrotin joukon hyödyntäen Amigan käyttöjärjestelmää. (Tämä  on  yksin-
  928. kertaisin mahdollinen ohjelma tähän tarkoitukseen, eikä ole  erityisen  no-
  929. pea, ainakaan ilman FPU:ta.) Ohjelman laskentaosuus pohjautui  muistaakseni
  930. vastaavaan BASIC-ohjelmaan. Kannattaa huomata, että ohjelma on vanha, suun-
  931. niteltu toimimaan Amigan käyttöjärjestelmän versiolla 1.3 eikä ole  muuten-
  932. kaan kovin joustava. 
  933.  
  934. #include <stdlib.h>
  935.  
  936. #include <exec/types.h>
  937. #include <intuition/intuition.h>
  938.  
  939. #include <proto/exec.h>
  940. #include <proto/intuition.h>
  941. #include <proto/graphics.h>
  942.  
  943. const struct NewScreen newscr = {
  944.   0, 0, 320, 256, 5, 0, 1,
  945.   CUSTOMSCREEN, NULL, NULL, NULL, NULL
  946. };
  947.  
  948. struct NewWindow newwin = {
  949.   0, 0, 320, 256, 0, 1, IDCMP_VANILLAKEY,
  950.   SIMPLE_REFRESH | ACTIVATE | BORDERLESS | RMBTRAP,
  951.   NULL, NULL, NULL, NULL, NULL, 0, 0, 320, 256, CUSTOMSCREEN
  952. };
  953.  
  954. const unsigned short colors[] = {
  955.   0x000, 0x058, 0x04a, 0x03c, 0x00f, 0x20f, 0x60e, 0x80d,
  956.   0xa0a, 0xf00, 0xf20, 0xf50, 0xf80, 0xfa0, 0xfc0, 0xff0,
  957.   0xef0, 0xdf0, 0xcf0, 0xbf0, 0xaf0, 0x9f0, 0x8f0, 0x7f0,
  958.   0x6e1, 0x4b3, 0x2a4, 0x095, 0x085, 0x076, 0x066, 0x067
  959. };
  960.  
  961. int
  962. main(int ac, char **av)
  963. {{
  964.   float xp, yp, y, x, X, Y, nx = -3, xx = 1.5, ny = -1.5, dv, xy;
  965.   struct RastPort *rp;
  966.   struct Screen *scr;
  967.   struct Window *win;
  968.   long wmask;
  969.   int i;
  970.  
  971.   if (scr = OpenScreen(&newscr)) {
  972.     LoadRGB4(&scr->ViewPort, colors, 32);
  973.     newwin.Screen = scr;
  974.     if (win = OpenWindow(&newwin)) {
  975.       rp = win->RPort;
  976.       wmask = 1 << win->UserPort->mp_SigBit;
  977.       xy = ny + (256 * (dv = (xx - nx) / 320));
  978.       for (Y = ny; Y <= xy && !(SetSignal(0, 0) & wmask); Y += dv) {
  979.         for (X = nx; X <= xx; X += dv) {
  980.           x = y = xp = yp = i = 0;
  981.           while (xp + yp <= 4 && i < 64) {
  982.             y = 2 * x * y + Y;
  983.             xp = (x = xp - yp + X) * x;
  984.             yp = y * y;
  985.             ++i;
  986.           }
  987.           SetAPen(rp, ((i < 64) ? i % 32 : 0));
  988.           WritePixel(rp, (long)((X - nx) / dv), (long)((Y - ny) / dv));
  989.         }
  990.       }
  991.       Wait(wmask);
  992.       while (GetMsg(win->UserPort))
  993.         ;
  994.       CloseWindow(win);
  995.     }
  996.     CloseScreen(scr);
  997.   }
  998.   return 0;
  999. }
  1000.